package fr.afcepf.atod16.groupe1.projet3;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Controlleur du framework type MVC2 :
 * - charge un fichier de config
 * - recoit les requetes HTTP
 * - rempli l'ActionForm correspondant
 * - execute l'Action définie
 * - affiche la page JSP demandé
 *
 * @author cedric
 */
public class ActionServlet extends HttpServlet {

    // Path une fois que le framework sera une librairie.   
    final static String PATH_FICHIER_XML = "../monstruts-config.xml";
    final static String PATH_FICHIER_XSD = "../monstruts-validator.xsd";
    HashMap<String, MonStrutsParams> monStruts;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        try {
            //Instanciation du validateur du fichier monstruts-validator.xsd
            URL urlXsd = Thread.currentThread().getContextClassLoader().getResource(PATH_FICHIER_XSD);
            File f = new File(urlXsd.getPath());
            SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            Schema schema = schemaFactory.newSchema(f);
            Validator validator = schema.newValidator();

            //Instantiation de l'objet servant de fichier de config
            monStruts = new HashMap<String, MonStrutsParams>();

            //Path du fichier config XML
            URL urlXml = Thread.currentThread().getContextClassLoader().getResource(PATH_FICHIER_XML);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            //Ouverture du document XML avec DOM
            Document doc = builder.parse(urlXml.getPath());
            validator.validate(new DOMSource(doc));

            //Parcours du fichier XML
            //Dans l'element monstruts-config
            Element racine = doc.getDocumentElement();

            //Recuperation de tout les elements 'action'
            NodeList actionsNode = racine.getElementsByTagName("action");
            //Iteration sur les elements 'action'
            for (int i = 0; i < actionsNode.getLength(); i++) {
                Element action = (Element) actionsNode.item(i);
                NodeList actionName = action.getElementsByTagName("action-name");
                NodeList urlPattern = action.getElementsByTagName("url-pattern");
                NodeList formName = action.getElementsByTagName("form-name");
                NodeList error = action.getElementsByTagName("error");
                NodeList fowards = action.getElementsByTagName("forward");

                MonStrutsParams param = new MonStrutsParams();
                Class actionNameClass = Class.forName(actionName.item(0).getTextContent());
                if (!Action.class.isAssignableFrom(actionNameClass)) {
                    System.out.println("Dans le premier Test");
                    throw new ServletException("La 25classe '" + actionNameClass + "' doit extends la Classe 'Action'");
                }
                param.setActionName(actionNameClass);
                param.setFormName(formName.item(0).getTextContent());
                //Set du path de l'erreur (optionnel)
                if (error.item(0) != null) {
                    param.setErrorPath(error.item(0).getTextContent());
                }

                HashMap<String, String> fowardsMap = new HashMap<String, String>();
                if (fowards.getLength() > 0) {
                    for (int j = 0; j < fowards.getLength(); j++) {
                        String pathFoward = fowards.item(j).getTextContent();
                        NamedNodeMap mapAttributs = fowards.item(j).getAttributes();
                        Attr attributeName = (Attr) mapAttributs.getNamedItem("name");
                        fowardsMap.put(attributeName.getValue(), pathFoward);
                    }
                }
                param.setFowards(fowardsMap);
                monStruts.put(urlPattern.item(0).getTextContent(), param);
            }


            //Recuperation des elements 'form'
            NodeList formsNode = racine.getElementsByTagName("form");
            //Iteration pour chaque element 'form'
            for (int i = 0; i < formsNode.getLength(); i++) {
                Element form = (Element) formsNode.item(i);
                String formClass = form.getElementsByTagName("form-class").item(0).getTextContent();
                String formName = form.getElementsByTagName("form-name").item(0).getTextContent();


                boolean forNameTrouver = false;

                Set<String> monStrutsKeys = monStruts.keySet();
                for (String formNameMonStruts : monStrutsKeys) {
                    MonStrutsParams param = monStruts.get(formNameMonStruts);
                    if (param.getFormName().equals(formName)) {
                        Class formClassC = Class.forName(formClass);
                        if (!ActionForm.class.isAssignableFrom(formClassC)) {
                            throw new ServletException("La classe '" + formClassC + "' doit extends la Classe 'ActionForm'");
                        }
                        param.setFormClass(formClassC);
                        forNameTrouver = true;
                        break;
                    }
                }
                //Si aucun elemnts correspondant au formName n'est retrouvé dans la hasmap, alors
                // le fichier XML est mal construit.
                if (forNameTrouver == false) {
                    throw new ServletException("Un elements dans la balise action doit obligatoirement etre referencé dans la balise form du fichier monstruts-config.xml");
                }

            }

            Set<String> keys = monStruts.keySet();
            for (String key : keys) {
                MonStrutsParams param = monStruts.get(key);
                if (param.getActionName() == null && param.getFormName() == null && param.getFormClass() == null) {
                    throw new ServletException("Les elements 'action-name', 'url-pattern' et 'form-class' sont obligatoire dans le fichier monstruts-config.xml");
                }

                HashMap hashMapFoward = param.getFowards();
                if (hashMapFoward.isEmpty()) {
                    throw new ServletException("Au moins 1 element 'foward' doit etre referencé pour chaque balise 'action' dans le fichier monstruts-config.xml");
                }

            }

        } catch (ClassNotFoundException ex) {
            throw new ServletException("Une classe referencée dans le monstruts-config.xml n'a pas été crée.", ex);
        } catch (ParserConfigurationException ex) {
            throw new ServletException("Probleme de parsingConfiguration , veuillez verifiez la validité du fichier monstruts-config.xml", ex);
        } catch (SAXException ex) {
            throw new ServletException("Probleme de parsing Sax, veuillez verifiez la validité du fichier monstruts-config.xml", ex);
        } catch (IOException ex) {
            throw new ServletException("Impossible d'ouvir le fichier monstruts-config.xml dans le repertoire WEB-INF ", ex);
        }

    }

    /**
     * Processes requests for both HTTP
     * <code>GET</code> and
     * <code>POST</code> methods.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // tmp
        init(this);
        
        // Determination du path d'appel
        String url = request.getRequestURI();
        String urlPattern = url.substring(url.indexOf("/", 1));

        // recherche de l'url pattern dans la map
        MonStrutsParams config = monStruts.get(urlPattern);
        if (config == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            PrintWriter out = response.getWriter();
            out.append("<h1>MonStruts</h1>");
            out.append("Erreur 404 : pas d'action définie pour cette url !");
            return;
        }

        // Population de l'ActionForm
        Class actionFormClass = config.getFormClass();
        ActionForm actionFormInstance;
        try {
            actionFormInstance = (ActionForm) actionFormClass.newInstance();
        } catch (InstantiationException ex) {
            throw new ServletException("La classe ActionForm n'est pas instanciable !");
        } catch (IllegalAccessException ex) {
            throw new ServletException("La classe ActionForm ou son constructeur vide n'est pas accessible !");
        }

        actionFormInstance = (ActionForm) MyBeanPopulate.populateBean(actionFormInstance, request.getParameterMap());

        // Appel de la fonction validateForm de l'ActionForm
        ActionErrors actionErrors = actionFormInstance.validateForm();
        actionFormInstance.setErrors(actionErrors);

        // Attachement de l'ActionForm au scope request
        request.setAttribute(config.getFormName(), actionFormInstance);

        // Si erreur, RequestDispacher vers errorPath et fin
        if (actionErrors.getActionMessages().size() > 0) {
            request.getRequestDispatcher(config.getErrorPath()).forward(request, response);
        } else {
            // Si pas d'erreur, appel de la fonction execute de l'Action
            Class actionClass = config.getActionName();
            Action actionInstance;
            try {
                actionInstance = (Action) actionClass.newInstance();
            } catch (InstantiationException ex) {
                throw new ServletException("La classe Action n'est pas instanciable !");
            } catch (IllegalAccessException ex) {
                throw new ServletException("La classe Action ou son constructeur vide n'est pas accessible !");
            }

            ActionMapping mapping = new ActionMapping();
            HashMap<String, String> forwards = config.getForwards();
            for (Map.Entry<String, String> entry : forwards.entrySet()) {
                mapping.add(new ActionForward(entry.getKey(), entry.getValue()));
            }
            ActionForward actionForward = actionInstance.execute(mapping, actionFormInstance, request, response);
            System.out.println(mapping);
            // RequestDispacher vers actionForward
            request.getRequestDispatcher(actionForward.getPath()).forward(request, response);
        }
    }

    /**
     * Handles the HTTP
     * <code>GET</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Handles the HTTP
     * <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns a short description of the servlet.
     *
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }
}
